package FSDB

import (
	"database/sql"
	"fmt"

	"logs"

	_ "github.com/lib/pq"
)

// FSDB介绍：
// 		此包对pq这个数据库驱动库做了一个简单的封装，便于使用常用的CRUD等方法，
// 		使用SQLOpen打开数据库，并获得这个方法返回的数据库实例，同样配置（端口、用户）的多个数据库实例不会有冲突，
// 		之后可以用这个实例调用如SQLQuery等方法，
// 		如需使用高级的或者不常用的方法，请调用InnerDB()方法获得数据库驱动内的*sql.DB以进行自定义操作。
// 															—— Ficow Shen , 2017.3.17

//PostgreSQL 官方文档列表：
//		https://www.postgresql.org/docs/manuals/
//PostgreSQL 8.2.3 中文文档（截止2017.3.15，最新版本为9.6.2）：
//		http://www.yiibai.com/manual/postgresql/
//关于基本的增删查改可参考：
//		http://www.yiibai.com/manual/postgresql/tutorial-sql.html

var curDB *sql.DB

// Open参数：
//		需要驱动的数据库、数据库登录配置字符串
//		------------------
//		port：数据库端口号，默认为5432。如果改了，这里一定要自定义；
//		user：数据库的登录帐号;
//		dbname：数据库的名字;
//		sslmode：安全验证模式;
const (
	user     = "postgres"
	password = "123456"
	dbname   = "ficow" //postgres ficow
	port     = "5432"
)

// Open 打开数据库，返回: 错误
func Open() error {

	sqlOpen := fmt.Sprintf("port=%s user=%s password=%s dbname=%s sslmode=disable", port, user, password, dbname)
	var err error
	// Open("postgres", "port=5432 user=pqgotest password=123456 dbname=pqgotest sslmode=disable")
	// Open("postgres", "postgres://pqgotest:password@localhost/pqgotest?sslmode=disable")
	curDB, err = sql.Open("postgres", sqlOpen)

	if err != nil {
		fmt.Println("SQLOpen failed")
		return err
	}

	logs.Print(DBInfo(), " opened")
	return nil
}

// Query 查询数据，返回：行数据 或者 错误
func Query(sql string, args ...interface{}) (*sql.Rows, error) {

	stmt, err := curDB.Prepare(sql)
	defer stmt.Close()
	if err != nil {
		return nil, err
	}
	rows, err := stmt.Query(args...)
	if err != nil {
		return nil, err
	}
	return rows, err
}

// Exec 执行SQL语句，返回：执行结果 或者 错误
func Exec(sql string, args ...interface{}) (sql.Result, error) {

	stmt, err := curDB.Prepare(sql)
	defer stmt.Close()
	if err != nil {
		// logs.Print(err.Error())
		return nil, err
	}
	res, err := stmt.Exec(args...)
	if err != nil {
		return nil, err
	}
	return res, nil
}

// BeginTx 开始事务，返回：事务上下文 或者 错误
func BeginTx() (*sql.Tx, error) {

	tx, err := curDB.Begin()
	if err != nil {
		return nil, err
	}
	return tx, nil
}

// QueryTx 参数：事务，SQL，SQL参数，返回：行数据 或者 错误
func QueryTx(tx *sql.Tx, sql string, args ...interface{}) (*sql.Stmt, *sql.Rows, error) {

	stmt, err := tx.Prepare(sql)
	// logs.Print("\n* tx:", tx, "\nstmt:", stmt, "\nsql:", sql, "\nargs[0]:", args)
	if err != nil {
		// logs.Print("* tx.Prepare： ", err.Error())
		return nil, nil, err
	}
	rows, err := stmt.Query(args...)
	if err != nil {
		// logs.Print("* stmt.Query： ", err.Error())
		return nil, nil, err
	}
	return stmt, rows, err
}

// ExecTx 参数：事务，SQL，SQL参数，返回：执行结果 或者 错误
func ExecTx(tx *sql.Tx, sql string, args ...interface{}) (sql.Result, error) {

	stmt, err := tx.Prepare(sql)
	defer stmt.Close()
	if err != nil {
		return nil, err
	}
	res, err := stmt.Exec(args...)
	if err != nil {
		return nil, err
	}
	return res, nil
}

// Close 数据库实例
func Close() error {
	err := curDB.Close()
	logs.Print(DBInfo(), "closed")
	return err
}

// DBInfo 返回当前数据库实例的以下信息
func DBInfo() string {
	return fmt.Sprintf("SQLDB(port:%s,user:%s,dbname:%s)", port, user, dbname)
}

// INSERT格式如下：
//		INSERT INTO weather VALUES ('San Francisco', 46, 50, 0.25, '1994-11-27');
// * 推荐使用格式：
//		INSERT INTO weather (city, temp_lo, temp_hi, prcp, date)
//		VALUES ('San Francisco', 43, 57, 0.0, '1994-11-29');
// * 大量导入数据可使用：
//		COPY weather FROM '/home/user/weather.txt';

// Delete格式如下：
//		DELETE FROM weather WHERE city = 'Hayward';
// 清空表：
//		DELETE FROM tablename;

// Select格式如下：
// 		SELECT DISTINCT city FROM weather
// 		WHERE city = 'San Francisco' AND prcp > 0.0
// 		ORDER BY city;
// * 表之间的连接：
// 		http://www.yiibai.com/manual/postgresql/tutorial-join.html
// * 聚集函数 - count(数目), sum(总和), avg(均值), max(最大值), min(最小值)：
// 		http://www.yiibai.com/manual/postgresql/tutorial-agg.html
// * 操作符 - IN、NOT IN、EXISTS、LIKE：
//		http://www.yiibai.com/manual/postgresql/functions.html
// 返回行数据的读取方法：
// 		for rows.Next() {
// 			var uid int
// 			var username string
// 			var department string
// 			var created string
// 			err = rows.Scan(&uid, &username, &department, &created)
// 			checkErr(err)
// 			fmt.Println("uid = ", uid, " name = ", username, " dep = ", department, " created = ", created)
// 		}

// Update格式如下：
// 		UPDATE weather
// 		SET temp_hi = temp_hi - 2,  temp_lo = temp_lo - 2
// 		WHERE date > '1994-11-28';

// CreateTable格式如下：
// 		CREATE TABLE weather (
//   		id				 int PRIMARY KEY,
//   		name            varchar(80),
// 		);

// 常用数据类型：
//		int、bigint、bool、varchar、date、real、double precision、jsonb
//		要求精确的计算(比如计算货币金额)，应使用 numeric 类型
// 关于数据类型可参考：
//		https://www.postgresql.org/docs/9.6/static/datatype.html
// * 关于json和jsonb格式：
//		https://www.postgresql.org/docs/9.6/static/datatype-json.html
// 常用约束：
//		UNIQUE、NOT NULL、PRIMARY KEY、REFERENCES + 表名、ON DELETE CASCADE
// 关于约束可参考：
//		https://www.postgresql.org/docs/9.6/static/datatype-json.html
//		https://www.postgresql.org/docs/9.6/static/functions-json.html

// DropTable格式如下：
// 		DROP table_name;

// CreateIndex格式如下：
// 		CREATE INDEX test1_id_index ON test1 (id);
// 索引参考链接：
// 		http://www.yiibai.com/manual/postgresql/sql-createindex.html#SQL-CREATEINDEX-CONCURRENTLY
// * 在创建索引的同时写入表，需要注意的问题：
// 		http://www.yiibai.com/manual/postgresql/sql-createindex.html#SQL-CREATEINDEX-CONCURRENTLY

// DropIndex格式如下：
// 		DROP index_name;

// 事务参考链接：
//		http://www.yiibai.com/manual/postgresql/tutorial-transactions.html
